package com.director.spring;

import com.director.core.DirectAction;
import com.director.core.DirectException;
import com.director.core.DirectMethod;
import com.director.core.DirectTransactionData;
import com.director.core.DirectTransactionResult;
import com.director.core.ExecutorAdapter;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.aop.Advisor;
import org.springframework.aop.framework.Advised;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.aop.support.DefaultPointcutAdvisor;
import org.springframework.aop.support.StaticMethodMatcherPointcut;
import org.springframework.util.ClassUtils;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
 * @author Simone Ricciardi
 * @version 1.0, 12/03/2011
 */
class AdvisedActionExecutorAdapter implements ExecutorAdapter {

   private Advised action;

   AdvisedActionExecutorAdapter(Advised action) {
      this.action = action;
   }

   @Override
   public DirectTransactionResult execute(DirectAction directAction, DirectMethod directMethod, DirectTransactionData data)
         throws DirectException {

      try {
         Object targetAction = this.action.getTargetSource().getTarget();
         ProxyFactory factory = new ProxyFactory(targetAction);
         factory.setProxyTargetClass(true);

         Advisor[] advisors = this.action.getAdvisors();
         for(Advisor advisor : advisors) {
            factory.addAdvisor(advisor);
         }

         DirectMethodInterceptor methodInterceptor = new DirectMethodInterceptor(directAction, directMethod, data);
         factory.addAdvisor(new DefaultPointcutAdvisor(new DirectMethodPointcut(directMethod), methodInterceptor));

         Object proxy = factory.getProxy(ClassUtils.getDefaultClassLoader());
         Object[] params = directMethod.getDefaultParameterValues();
         directMethod.getMethod().invoke(proxy, params);

         DirectTransactionResult result = methodInterceptor.result;
         methodInterceptor.result = null;
         return result;
      } catch(InvocationTargetException e) {
         String message = "Cannot invoke the action method " + directMethod + " of the direct class " + directAction;
         throw new DirectException(message, e.getTargetException());
      } catch(Throwable e) {
         String message = "Cannot invoke the action method " + directMethod + " of the direct class " + directAction;
         throw new DirectException(message, e);
      }
   }

   static class DirectMethodPointcut extends StaticMethodMatcherPointcut {

      DirectMethod directMethod;

      DirectMethodPointcut(DirectMethod directMethod) {
         this.directMethod = directMethod;
      }

      @Override
      public boolean matches(Method method, Class<?> targetClass) {
         return directMethod.getMethod().equals(method);
      }

      @Override
      public boolean equals(Object o) {
         if(this == o) {
            return true;
         }
         if(o == null || getClass() != o.getClass()) {
            return false;
         }

         DirectMethodPointcut that = (DirectMethodPointcut) o;

         return directMethod.equals(that.directMethod);
      }

      @Override
      public int hashCode() {
         return directMethod.hashCode();
      }
   }

   static class DirectMethodInterceptor implements MethodInterceptor {

      DirectAction directAction;
      DirectMethod directMethod;
      DirectTransactionData data;
      DirectTransactionResult result;

      public DirectMethodInterceptor(DirectAction directAction, DirectMethod directMethod, DirectTransactionData data) {
         this.directAction = directAction;
         this.directMethod = directMethod;
         this.data = data;
      }

      @Override
      public Object invoke(MethodInvocation invocation) throws Throwable {

         DefaultActionExecutorAdapter executorAdapter = new DefaultActionExecutorAdapter(invocation.getThis());
         this.result = executorAdapter.execute(this.directAction, this.directMethod, this.data);
         return null;
      }

      @Override
      public boolean equals(Object o) {
         if(this == o) {
            return true;
         }
         if(o == null || getClass() != o.getClass()) {
            return false;
         }

         DirectMethodInterceptor that = (DirectMethodInterceptor) o;

         return directAction.equals(that.directAction) && directMethod.equals(that.directMethod);

      }

      @Override
      public int hashCode() {
         int result = directAction.hashCode();
         result = 31 * result + directMethod.hashCode();
         return result;
      }
   }
}
